/*
 * The MIT License (MIT)
 *
 * Copyright (C) 2019, CosmicMind, Inc. <http://cosmicmind.com>.
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

import UIKit

public class MotionAnimation {
  /// A reference to the callback that applies the MotionAnimationState.
  internal let apply: (inout MotionAnimationState) -> Void
  
  /**
   An initializer that accepts a given callback.
   - Parameter applyFunction: A given callback.
   */
  init(applyFunction: @escaping (inout MotionAnimationState) -> Void) {
    apply = applyFunction
  }
}

public extension MotionAnimation {
  /**
   Animates a view's current background color to the
   given color.
   - Parameter color: A UIColor.
   - Returns: A MotionAnimation.
   */
  static func background(color: UIColor) -> MotionAnimation {
    return MotionAnimation {
      $0.backgroundColor = color.cgColor
    }
  }
  
  /**
   Animates a view's current border color to the
   given color.
   - Parameter color: A UIColor.
   - Returns: A MotionAnimation.
   */
  static func border(color: UIColor) -> MotionAnimation {
    return MotionAnimation {
      $0.borderColor = color.cgColor
    }
  }
  
  /**
   Animates a view's current border width to the
   given width.
   - Parameter width: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func border(width: CGFloat) -> MotionAnimation {
    return MotionAnimation {
      $0.borderWidth = width
    }
  }
  
  /**
   Animates a view's current corner radius to the
   given radius.
   - Parameter radius: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func corner(radius: CGFloat) -> MotionAnimation {
    return MotionAnimation {
      $0.cornerRadius = radius
    }
  }
  
  /**
   Animates a view's current transform (perspective, scale, rotate)
   to the given one.
   - Parameter _ transform: A CATransform3D.
   - Returns: A MotionAnimation.
   */
  static func transform(_ transform: CATransform3D) -> MotionAnimation {
    return MotionAnimation {
      $0.transform = transform
    }
  }
  
  /**
   Animates a view's current rotation to the given x, y,
   and z values.
   - Parameter x: A CGFloat.
   - Parameter y: A CGFloat.
   - Parameter z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func rotate(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionAnimation {
    return MotionAnimation {
      var t = $0.transform ?? CATransform3DIdentity
      t = CATransform3DRotate(t, CGFloat(Double.pi) * x / 180, 1, 0, 0)
      t = CATransform3DRotate(t, CGFloat(Double.pi) * y / 180, 0, 1, 0)
      $0.transform = CATransform3DRotate(t, CGFloat(Double.pi) * z / 180, 0, 0, 1)
    }
  }
  
  /**
   Animates a view's current rotation to the given point.
   - Parameter _ point: A CGPoint.
   - Parameter z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func rotate(_ point: CGPoint, z: CGFloat = 0) -> MotionAnimation {
    return .rotate(x: point.x, y: point.y, z: z)
  }
  
  /**
   Rotate 2d.
   - Parameter _ z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func rotate(_ z: CGFloat) -> MotionAnimation {
    return .rotate(z: z)
  }
  
  /**
   Animates a view's current spin to the given x, y,
   and z values.
   - Parameter x: A CGFloat.
   - Parameter y: A CGFloat.
   - Parameter z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func spin(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionAnimation {
    return MotionAnimation {
      $0.spin = (x, y, z)
    }
  }
  
  /**
   Animates a view's current spin to the given point.
   - Parameter _ point: A CGPoint.
   - Parameter z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func spin(_ point: CGPoint, z: CGFloat = 0) -> MotionAnimation {
    return .spin(x: point.x, y: point.y, z: z)
  }
  
  /**
   Spin 2d.
   - Parameter _ z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func spin(_ z: CGFloat) -> MotionAnimation {
    return .spin(z: z)
  }
  
  /**
   Animates the view's current scale to the given x, y, and z scale values.
   - Parameter x: A CGFloat.
   - Parameter y: A CGFloat.
   - Parameter z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func scale(x: CGFloat = 1, y: CGFloat = 1, z: CGFloat = 1) -> MotionAnimation {
    return MotionAnimation {
      $0.transform = CATransform3DScale($0.transform ?? CATransform3DIdentity, x, y, z)
    }
  }
  
  /**
   Animates the view's current x & y scale to the given scale value.
   - Parameter _ xy: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func scale(_ xy: CGFloat) -> MotionAnimation {
    return .scale(x: xy, y: xy)
  }
  
  /**
   Animates a view equal to the distance given by the x, y, and z values.
   - Parameter x: A CGFloat.
   - Parameter y: A CGFloat.
   - Parameter z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func translate(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionAnimation {
    return MotionAnimation {
      $0.transform = CATransform3DTranslate($0.transform ?? CATransform3DIdentity, x, y, z)
    }
  }
  
  /**
   Animates a view equal to the distance given by a point and z value.
   - Parameter _ point: A CGPoint.
   - Parameter z: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func translate(_ point: CGPoint, z: CGFloat = 0) -> MotionAnimation {
    return .translate(x: point.x, y: point.y, z: z)
  }
  
  /**
   Animates a view's current position to the given point.
   - Parameter _ point: A CGPoint.
   - Returns: A MotionAnimation.
   */
  static func position(_ point: CGPoint) -> MotionAnimation {
    return MotionAnimation {
      $0.position = point
    }
  }
  
  /**
   Animates a view's current position to the given x and y values.
   - Parameter x: A CGloat.
   - Parameter y: A CGloat.
   - Returns: A MotionAnimation.
   */
  static func position(x: CGFloat, y: CGFloat) -> MotionAnimation {
    return .position(CGPoint(x: x, y: y))
  }
  
  /// Fades the view in during an animation.
  static var fadeIn = MotionAnimation.fade(1)
  
  /// Fades the view out during an animation.
  static var fadeOut = MotionAnimation.fade(0)
  
  /**
   Animates a view's current opacity to the given one.
   - Parameter _ opacity: A Double.
   - Returns: A MotionAnimation.
   */
  static func fade(_ opacity: Double) -> MotionAnimation {
    return MotionAnimation {
      $0.opacity = opacity
    }
  }
  
  /**
   Animates a view's current zPosition to the given position.
   - Parameter _ position: An Int.
   - Returns: A MotionAnimation.
   */
  static func zPosition(_ position: CGFloat) -> MotionAnimation {
    return MotionAnimation {
      $0.zPosition = position
    }
  }
  
  /**
   Animates a view's current size to the given one.
   - Parameter _ size: A CGSize.
   - Returns: A MotionAnimation.
   */
  static func size(_ size: CGSize) -> MotionAnimation {
    return MotionAnimation {
      $0.size = size
    }
  }
  
  /**
   Animates the view's current size to the given width and height.
   - Parameter width: A CGFloat.
   - Parameter height: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func size(width: CGFloat, height: CGFloat) -> MotionAnimation {
    return .size(CGSize(width: width, height: height))
  }
  
  /**
   Animates a view's current shadow path to the given one.
   - Parameter path: A CGPath.
   - Returns: A MotionAnimation.
   */
  static func shadow(path: CGPath) -> MotionAnimation {
    return MotionAnimation {
      $0.shadowPath = path
    }
  }
  
  /**
   Animates a view's current shadow color to the given one.
   - Parameter color: A UIColor.
   - Returns: A MotionAnimation.
   */
  static func shadow(color: UIColor) -> MotionAnimation {
    return MotionAnimation {
      $0.shadowColor = color.cgColor
    }
  }
  
  /**
   Animates a view's current shadow offset to the given one.
   - Parameter offset: A CGSize.
   - Returns: A MotionAnimation.
   */
  static func shadow(offset: CGSize) -> MotionAnimation {
    return MotionAnimation {
      $0.shadowOffset = offset
    }
  }
  
  /**
   Animates a view's current shadow opacity to the given one.
   - Parameter opacity: A Float.
   - Returns: A MotionAnimation.
   */
  static func shadow(opacity: Float) -> MotionAnimation {
    return MotionAnimation {
      $0.shadowOpacity = opacity
    }
  }
  
  /**
   Animates a view's current shadow radius to the given one.
   - Parameter radius: A CGFloat.
   - Returns: A MotionAnimation.
   */
  static func shadow(radius: CGFloat) -> MotionAnimation {
    return MotionAnimation {
      $0.shadowRadius = radius
    }
  }
  
  /**
   Animates the views shadow offset, opacity, and radius. 
   - Parameter offset: A CGSize. 
   - Parameter opacity: A Float.
   - Parameter radius: A CGFloat.
   */
  static func depth(offset: CGSize, opacity: Float, radius: CGFloat) -> MotionAnimation {
    return MotionAnimation {
      $0.shadowOffset = offset
      $0.shadowOpacity = opacity
      $0.shadowRadius = radius
    }
  }
  
  /**
   Animates the views shadow offset, opacity, and radius.
   - Parameter _ depth: A tuple (CGSize, FLoat, CGFloat).
   */
  static func depth(_ depth: (CGSize, Float, CGFloat)) -> MotionAnimation {
    return .depth(offset: depth.0, opacity: depth.1, radius: depth.2)
  }
  
  /**
   Available in iOS 9+, animates a view using the spring API,
   given a stiffness and damping.
   - Parameter stiffness: A CGFlloat.
   - Parameter damping: A CGFloat.
   - Returns: A MotionAnimation.
   */
  @available(iOS 9, *)
  static func spring(stiffness: CGFloat, damping: CGFloat) -> MotionAnimation {
    return MotionAnimation {
      $0.spring = (stiffness, damping)
    }
  }
  
  /**
   The duration of the view's animation. If a duration of 0 is used,
   the value will be converted to 0.01, to give a close to zero value.
   - Parameter _ duration: A TimeInterval.
   - Returns: A MotionAnimation.
   */
  static func duration(_ duration: TimeInterval) -> MotionAnimation {
    return MotionAnimation {
      $0.duration = duration
    }
  }
  
  /**
   Delays the animation of a given view.
   - Parameter _ time: TimeInterval.
   - Returns: A MotionAnimation.
   */
  static func delay(_ time: TimeInterval) -> MotionAnimation {
    return MotionAnimation {
      $0.delay = time
    }
  }
  
  /**
   Sets the view's timing function for the animation.
   - Parameter _ timingFunction: A CAMediaTimingFunction.
   - Returns: A MotionAnimation.
   */
  static func timingFunction(_ timingFunction: CAMediaTimingFunction) -> MotionAnimation {
    return MotionAnimation {
      $0.timingFunction = timingFunction
    }
  }
  
  /**
   Creates a completion block handler that is executed once the animation
   is done.
   - Parameter _ execute: A callback to execute once completed.
   */
  static func completion(_ execute: @escaping () -> Void) -> MotionAnimation {
    return MotionAnimation {
      $0.completion = execute
    }
  }
}
